﻿#include "font.h"
#include <vector>
#include <map>
#include "context.h"
#include "texture.h"
#include "target.h"
#include "ft2build.h"
#include "freetype/freetype.h"
#include "freetype/ftcache.h"

using namespace std;
using namespace anip;

struct FontManager::Impl
{
	typedef struct
	{
		string path;
		int index;
	} FaceIDR, * FaceID;

	static FT_Library _lib;
	static FTC_Manager _manager;
	static FTC_CMapCache _mapc;
	static FTC_ImageCache _imgc;

	static map<string, FT_Face> _faces;
	static map<string, FaceIDR> _ids;
	static bool _finalized;

	static void FTErrorCheck(FT_Error ret, const char* stmt);

	static int _typeRequest(void* id, FT_Library lib, void* data, FT_Face* f);
};

struct Font::Impl
{
	vector<FontManager::Impl::FaceID> _ids{};
	int _size = -1;

	bool _loadCharFrom(
		const FontManager::Impl::FaceID id,
		int code,
		Font::CharData& data) const;
};

/* ------------------------ FontManager --------------------------- */

#define FTEC(stmt) FontManager::Impl::FTErrorCheck(stmt, #stmt)

void FontManager::Impl::FTErrorCheck(FT_Error ret, const char* stmt)
{
	if (ret != FT_Err_Ok)
	{
		char buf[500]; const char* str = FT_Error_String(ret);
		if (str == nullptr) str = "unknown";
		snprintf(buf, 500, "Freetype error: '%s' returned %d (%s)", stmt, ret, str);
		throw runtime_error(buf);
	}
}

FT_Library FontManager::Impl::_lib = nullptr;
FTC_Manager FontManager::Impl::_manager = nullptr;
FTC_CMapCache FontManager::Impl::_mapc = nullptr;
FTC_ImageCache FontManager::Impl::_imgc = nullptr;

map<string, FT_Face> FontManager::Impl::_faces;
map<string, FontManager::Impl::FaceIDR> FontManager::Impl::_ids;
bool FontManager::Impl::_finalized = false;

int FontManager::Impl::_typeRequest(void* id, FT_Library lib, void* data, FT_Face* f)
{
	FaceID fid = (FaceID)id;
	FTEC(FT_New_Face(lib, fid->path.c_str(), fid->index, f));
	return 0;
}

void FontManager::init()
{
	FTEC(FT_Init_FreeType(&Impl::_lib));
	FTEC(FTC_Manager_New(Impl::_lib, 10, 0, 0, Impl::_typeRequest, nullptr, &Impl::_manager));
	FTEC(FTC_CMapCache_New(Impl::_manager, &Impl::_mapc));
	FTEC(FTC_ImageCache_New(Impl::_manager, &Impl::_imgc));
}

Font FontManager::get(const string& name, int size)
{
	assert(Impl::_lib != nullptr);

	Font f;
	f._pimpl->_ids.push_back(&Impl::_ids[name]);
	f._pimpl->_size = size;
	return f;
}

Font FontManager::get(initializer_list<const string> names, int size)
{
	assert(Impl::_lib != nullptr && names.size() >= 1);

	vector<Impl::FaceID> fall;
	for (auto& i : names)
		fall.push_back(&Impl::_ids[i]);

	Font f;
	f._pimpl->_ids = fall;
	f._pimpl->_size = size;
	return f;
}

void FontManager::addFace(string name, string file, int index)
{
	assert(index >= 0);
	Impl::_ids.insert(make_pair(name, Impl::FaceIDR{ file, index }));
}

void FontManager::finalize()
{
	FT_Done_FreeType(Impl::_lib);
	Impl::_finalized = true;
}

/* ------------------------ Font --------------------------- */

Font::Font() :
	_pimpl(new Impl()), size(_pimpl->_size)
{}

Font::Font(const Font& obj):
	_pimpl(new Impl()), size(_pimpl->_size)
{
	_pimpl->_ids = obj._pimpl->_ids;
	_pimpl->_size = obj.size;
}

const Font& Font::operator=(const Font& obj)
{
	_pimpl->_ids = obj._pimpl->_ids;
	size = obj.size;
	return *this;
}

bool Font::Impl::_loadCharFrom(
	const FontManager::Impl::FaceID id, 
	int code, 
	Font::CharData& data) const
{
	if (id == nullptr) return false;

	unsigned int index = FTC_CMapCache_Lookup(FontManager::Impl::_mapc, id, -1, code);
	if (index == 0) return false;

	FT_BitmapGlyph glyph;
	FTC_ImageTypeRec type;
	type.face_id = id;
	type.width = _size;
	type.height = _size;
	type.flags = FT_LOAD_RENDER;

	FTEC(FTC_ImageCache_Lookup(FontManager::Impl::_imgc, &type, index, (FT_Glyph*)&glyph, nullptr));

	FT_Bitmap& bitmap = glyph->bitmap;
	data.width = bitmap.width;
	data.height = bitmap.rows;
	data.hBearingX = glyph->left;
	data.hBearingY = glyph->top;
	data.hAdvance = glyph->root.advance.x >> 16; // advance.x is in 16.16 fixed point format
	data.grayMap = new unsigned char[bitmap.width * bitmap.rows];
	int i = 0;
	for (unsigned int y = 0; y < bitmap.rows; y++)
		for (unsigned int x = 0; x < bitmap.width; x++)
		{
			unsigned int offset = y * bitmap.pitch + x;
			data.grayMap[i++] = bitmap.buffer[offset];
		}

	return true;
}

Font::~Font() = default;

bool Font::isNull() const
{
	return size < 0;
}

bool Font::loadChar(int code, Font::CharData& data) const
{
	for (auto i : _pimpl->_ids)
		if (_pimpl->_loadCharFrom(i, code, data))
			return true;
	return false;
}

#undef FTEC

/* ------------------------ TextRenderer --------------------------- */

struct TextRenderer::StringLayout::Internal
{
	WordBreaker breaker;
	Character* chars;
	vector<Character*> word;
	int w = 0, wordw = 0, x = 0, y = 0, col = 0, line = 0, lineh = 0;
	float scaling = 1;
	vec2 measure{};
	const wstring& str;
	Font f;
	const Options& opt;

	Internal(const wstring& _str, const Font& _f, const Options& _opt, float _scale);
	~Internal();

	void newLine();
	void submitWord();
	void layout();

	Texture2D* render() const;
};

const TextRenderer::WordBreaker TextRenderer::noBreak = TextRenderer::_noBreak;
const TextRenderer::WordBreaker TextRenderer::defaultBreaker = TextRenderer::_defaultBreak;

TextRenderer::StringLayout::Internal::Internal(
	const wstring& _str, const Font& _f, const Options& _opt, float _scale) :
		str(_str), f(_f), opt(_opt), scaling(_scale)
{
	breaker = opt.breaker == nullptr ? defaultBreaker : opt.breaker;
	chars = new Character[str.length()];
}

TextRenderer::StringLayout::Internal::~Internal()
{
	delete[] chars;
}

void TextRenderer::StringLayout::Internal::newLine()
{
	x = 0;
	col = 0;
	y += lineh;
	line++;
}

void TextRenderer::StringLayout::Internal::submitWord()
{
	// first check if it overflows the line, but special-case if
	// we're at the beginning of a line (so the word is longer than 
	// the whole line)
	if (opt.maxWidth > 0 && x + wordw > opt.maxWidth && x != 0)
		newLine();

	for (unsigned int j = 0; j < word.size(); j++)
	{
		int adv = word[j]->data.hAdvance;
		if (opt.maxWidth > 0 && x + adv > opt.maxWidth)
			newLine();
		if (opt.ignoreInitialWhitespace && x == 0 && iswblank(word[j]->ch))
		{
			word[j]->ch = 0;
			continue;
		}
		word[j]->x = x;
		word[j]->y = y;
		word[j]->col = col;
		word[j]->line = line;
		x += adv;
		col++;
		lineh = max(lineh, f.size - word[j]->data.hBearingY + word[j]->data.height);
		if (x > w) w = x + 2;
	}
	word.clear();
	wordw = 0;
}

void TextRenderer::StringLayout::Internal::layout()
{
	for (unsigned int i = 0; i < str.length(); i++)
	{
		if (str[i] == L'\n')
		{
			submitWord();
			chars[i].ch = str[i];
			chars[i].col = col;
			chars[i].line = line;
			chars[i].x = x;
			chars[i].y = y;
			newLine();
			continue;
		}

		if (!f.loadChar(str[i], chars[i].data)) continue;
		chars[i].ch = str[i];

		if (word.size() > 0 && breaker(word[word.size() - 1]->ch, str[i]))
			submitWord();

		word.push_back(&chars[i]);
		wordw += chars[i].data.hAdvance;
	}
	if (word.size() > 0)
		submitWord();

	int lineHeight = f.size + opt.lineSpacing;
	int h = line * lineHeight + lineh;
	measure = { w, h };
}

Texture2D* TextRenderer::StringLayout::Internal::render() const
{
	if (w == 0)
		return nullptr;

	int h = (int)measure.y;
	auto empty = new unsigned char[w * h];
	memset(empty, 0, w * h);

	TextureOptions topt;
	topt.format = ColorFormat::Red;
	Texture2D* tex = Texture2D::createFromData(w, h, empty, &topt);
	delete[] empty;

	for (unsigned int i = 0; i < str.length(); i++)
	{
		Character& c = chars[i];
		if (c.ch == 0) continue;

		tex->setPartialData(
			max(0, c.x + c.data.hBearingX),
			c.y + f.size - c.data.hBearingY,
			c.data.width,
			c.data.height,
			c.data.grayMap);
	}

	return tex;
}

TextRenderer::StringLayout::StringLayout(
	const wstring& _str, const Font& _f, const Options& _opt, float _scale) :
		_internal(new Internal(_str, _f, _opt, _scale)) {}

vec2 anip::TextRenderer::StringLayout::dataSize() const
{
    return _internal->measure;
}

vec2 anip::TextRenderer::StringLayout::drawingSize() const
{
    return _internal->measure / _internal->scaling;
}

TextRenderer::StringLayout::~StringLayout()
{
	delete _internal;
}

TextRenderer::StringLayout* TextRenderer::layoutString(
	const wstring& str, const Font& f, const Options& opt)
{
	auto layout = new StringLayout(str, f, opt);
	layout->_internal->layout();
	return layout;
}

TextRenderer::StringLayout *anip::TextRenderer::layoutPixelPerfectString(
	const wstring &str, const Font &f, const Options &opt, const RenderBuffer *target)
{
	if (target == nullptr) target = &Context::currentTarget();
	Font ff(f); Options options(opt);
	vec2 zoom = target->transform().zoom;
	float factor = max(zoom.x, zoom.y);
	ff.size *= factor;
	if (opt.maxWidth > 0)
		options.maxWidth *= factor;
    auto layout = new StringLayout(str, ff, options, factor);
	layout->_internal->layout();
	return layout;
}

Texture2D* TextRenderer::renderString(const wstring& str, const Font& f, const Options& opt)
{
	auto layout = new StringLayout(str, f, opt);
	layout->_internal->layout();
	Texture2D* tex = layout->_internal->render();
	delete layout;
	return tex;
}

Texture2D* anip::TextRenderer::renderString(const StringLayout& layout)
{
	return layout._internal->render();
}

const TextRenderer::Character* TextRenderer::getLayout(const StringLayout& layout, int& len)
{
	len = layout._internal->str.length();
	return layout._internal->chars;
}